<?php

class Crud_Acl extends Zend_Acl 
{
	/**
	 * @var Crud_Acl
	 */
	protected static $_instance;

	/**
	 * @return Crud_Acl
	 */
	public static function getInstance()
	{
		if (!self::$_instance) {
			self::$_instance = new self();
		}
		return self::$_instance;
	}
	
	/**
	 * Protected constructor, cannot be called directly, initialize
	 * only via getInstance().
	 */
	protected function __construct()
	{
		// fetch all info about permissions from db
		$entitiesRowset = AdminSectionPeer::getInstance()->fetchAll();
		$roleRowset = AdminRolePeer::getInstance()->fetchAll();
		$groupRowset = AdminSectionGroupPeer::getInstance()->fetchAll();
		$role2groupPermissionRowset = AdminRole2sectionGroupPeer::getInstance()->fetchAll();
		
		
		$entities = array();
		$roles = array();
		$sectionGroupArray = array();
		$r2gPermissionArray = array();
		$dummy = new Zend_Acl_Resource('dummy');
		$this->addResource($dummy);
		
  		// form permission array for role-group
		foreach ($role2groupPermissionRowset as $row) {
			$roleId = $row->getRoleId();
			$groupId = $row->getGroupId();
			$permissions = $row->getPermissions();
			$r2gPermissionArray[$groupId][$roleId] = $permissions;
		}

		// store group permissions for all role-section by default
		$groupPermissionArray = array();
		foreach ($groupRowset as $groupRow) {
			if (!isset($r2gPermissionArray[$groupRow->getId()])) {
				continue;
			}
			$adminSectionRowset = $groupRow->getAdminSectionRowsetByGroupId();
			$roleIds = array_keys($r2gPermissionArray[$groupRow->getId()]);
			foreach ($roleIds as $roleId) {
				$permission =  $r2gPermissionArray[$groupRow->getId()][$roleId];
				foreach ($adminSectionRowset as $adminSectionRow) {
					$groupPermissionArray[$roleId][$adminSectionRow->getId()] = $permission;
				}
			}
		}
		
		// create acl entities and resources
		foreach ($roleRowset as $row) {
			$role = new Zend_Acl_Role($row->getKey());
			$this->addRole($role);
			$roles[$row->getId()] = $role;
			$this->deny($role, $dummy);
		}
		
		foreach ($entitiesRowset as $row) {
			$resource = new Zend_Acl_Resource($row->getKey());
			$this->addResource($resource);
			$entities[$row->getId()] = $resource;
			$sectionGroupArray[$row->getId()] = $row->getGroupId();
		}
		
		$permissionRowset = AdminRole2sectionPeer::getInstance()->fetchAll();
		
		// apply section permissions above group permissions
		foreach ($permissionRowset as $row) {
			$roleId = $row->getRoleId();
			$sectionId = $row->getSectionId();
			$groupId = $sectionGroupArray[$sectionId];
			$role = $roles[$roleId];
			$entity = $entities[$sectionId];
			
			$sectionPermission = $row->getPermissions();
			
			if (isset($groupPermissionArray[$roleId][$sectionId])) {
				$groupPermission = $groupPermissionArray[$roleId][$sectionId];
			} else {
				$groupPermission = '0000';
			}
			$permission = $this->_mergePermissions($sectionPermission, $groupPermission);
			$groupPermissionArray[$roleId][$sectionId] = $permission;
		}

		// put final rules to crud
		foreach ($roleRowset as $role) {
			$roleId = $role->getId();
			$role = $roles[$roleId];
			if (!isset($groupPermissionArray[$roleId])) {
				continue;
			}
			foreach ($groupPermissionArray[$roleId] as $sectionId => $permission) {
				$entity = $entities[$sectionId];
				$perm = $this->_getPermissionArrayByKey($permission);
				if (!$perm) {
					$this->deny($role, $entity);
				} else {
					$this->allow($role, $entity, $perm);
				}
			}
		}
	}
	
	/**
	 * Сливает 2 строки прав в формате xxxx, где x это 0 или 1.
	 * При этом единичка заменяет 0. 
	 * Примеры:
	 * 0000 + 1111 = 1111
	 * 1001 + 1000 = 1001
	 * 1000 + 0110 = 1110
	 *
	 * @param string $permArray1
	 * @param string $permArray2
	 * @return string
	 */
	protected function _mergePermissions($permArray1, $permArray2) {
		if (!strlen($permArray1)) {
			return $permArray2;
		}
		
		if (!strlen($permArray2)) {
			return $permArray1;
		}
		
		$count = strlen($permArray1);
		$result = array();
		for ($i = 0;$i < $count; $i++) {
			$result[$i] = $permArray1[$i] | $permArray2[$i];		
		}
		
		return implode('', $result);
	}
	
	/**
	 * Берет ключ, состоящий из 4х цифр, обозначающих права (0000 или 1111).
	 * Возвращает массив из 0 - 4х элеметов. Элементы могут быть create, read, update, delete. 
	 * 
	 * @param string $key
	 * @return array
	 */
	protected function _getPermissionArrayByKey($key) {
		$permissions = array();
		$maxPerms = array('create', 'read', 'update', 'delete');
		for ($i = 0; $i < strlen($key); $i++) {
			if ($key[$i]) {
				$permissions[] = $maxPerms[$i];
			}
		}
		return $permissions;
	}
}